##------------------------------------------------------------------------------
##
##	yasco-1.0 - Yet Another (Java)Script Obfuscator
##	Copyright (C) 2008-2011 Oliver Schieche <post@oliver-schieche.de>
##
##	Makes use of Yapp
##	Parse::Yapp - Copyright (c) 1998-2001, Francois Desarmenien, all right reserved.
##
##	$Id: Compress4.pm 12 2012-10-01 13:57:30Z goresplatter1844@gmail.com $
##------------------------------------------------------------------------------
package Yasco::Filter::Compress4;

use strict;
use vars qw[@ISA];

use Carp qw/confess/;
use Data::Dumper;
##------------------------------------------------------------------------------
BEGIN {
	require Yasco::Filter;
	@ISA = qw[Yasco::Filter];

	open FILE, '<', __FILE__ or die __FILE__ . ": $!";
	$Yasco::decompressor4 = [map {s/^\s*|\s*$//og;$_} split /^=====/m, (
		[split /^__END__/m, do{local($/); <FILE>}]->[1]
	)];
	close FILE;
}
##------------------------------------------------------------------------------
sub tokens {'ZZA_COMPRESS'}
sub max($$) {$_[0] > $_[1] ? $_[0] : $_[1]}

sub MAX_COMPRESS_BIT() {6}

use constant MIN_LENGTH		=> 3;
use constant MAX_WINDOW		=> 65535;
use constant MAX_LOOK_BACK	=> 1023;
##------------------------------------------------------------------------------
sub new
	{
	    my $self  = shift;
	    my $options = shift;
	    $self = $self->SUPER::new($options);

	    $self->{verbose} ||= 0;
		$self->{block_size} ||= 96;

	    $self
	}
##------------------------------------------------------------------------------
sub execute {}
##------------------------------------------------------------------------------
sub get_token()
	{
		my $self = shift;
		my ($o,$text) = @_;

		for ($text) {
			m{^//!\[NO_EXPORTS]} and do {
				$self->{verbose} && print STDERR "Using new Function() instead of eval()\n";
				$self->{NO_EXPORTS} = 1;
				return undef;
			};
		}

		undef
	}
##------------------------------------------------------------------------------
sub post_process
	{
		my ($self,$o,$code) = @_;
		my (@list,@res,$n,$bit,$tree,$bytes_pre);

		$bytes_pre = undef;

		print STDERR "\n" if $self->{verbose};

		$self->{input_name} = $o->option('file');

		$self->make_huffman_tree($code);
		printf STDERR "Tree size: %d characters.\n", 0+keys %{$self->{chars}} if $self->{verbose};

		if (1) {
			$bytes_pre = length $$code;

			print STDERR "\tEncoding: \x1B[s" if $self->{verbose};
			$$code = $self->_encode($$code);
			print STDERR "\n" if $self->{verbose};

			$self->make_huffman_tree($code);
			printf STDERR "Tree size after encode: %d characters.\n", 0+keys %{$self->{chars}} if $self->{verbose};
		}

		@list = split //, $$code;

		$bit = 0;
		$n = 0;

		print STDERR "\tCompressing: " if $self->{verbose};

		my $last5 = $self->{verbose} && 1 < $self->{verbose};
		my $dump_last = '
			printf STDERR "%s %3d %02X\t", $res[-1], ord($res[-1]), ord($res[-1]);
			printf STDERR "%3d ", (ord($res[-1]) & (1<<$_)) foreach(0..MAX_COMPRESS_BIT);
			print STDERR " | ";
			print STDERR (ord($res[-1]) & (1<<$_))?1:0 foreach(0..MAX_COMPRESS_BIT);
			print STDERR " ";
			print STDERR ($n & (1<<$_))?1:0 foreach(0..$bit-1);
			print STDERR "." foreach($bit..MAX_COMPRESS_BIT);
			printf STDERR "\t%s> %s [%s]\n", $res[-1], $c, join(" ", @code)';

		print STDERR "\x1B[s" if $self->{verbose};

		for(my ($index,$lastPerc,$length)=(0,0,0+@list);$index < $length;$index++)
			{
				my $c = $list[$index];
				my @code = @{$self->{chars}->{$c}};

				if ($last5 && $index >= $length - 5)
					{
						print STDERR "\n";
						eval $dump_last;
					}
				elsif ($self->{verbose} && $lastPerc != int(100 * $index / $length))
					{
						$lastPerc = int(100 * $index / $length);
						printf STDERR "\x1B[u\x1B[K%d%%", $lastPerc;
					}

				foreach (@code) {
					next if 0 == $_;
					$n |= (1 << $bit);
				} continue {
					if(MAX_COMPRESS_BIT < ++$bit) {
						die 'WTF' if $n > 0x7F;

#						if ($last5 && $index >= $length - 5)
#							{
#								print STDERR ' 'x41;
#								print STDERR '[PUSH] ';
#								print STDERR (($n & (1<<$_))?'1':'0') foreach(0..7);
#								print STDERR "\n";
#							}

						push @res, chr($n);
						$n = 0;
						$bit = 0;
					}
				}

				if ($last5 && $index >= $length - 5)
					{
						eval $dump_last;
					}
			}

		print STDERR "\n" if $self->{verbose};

	#	Here be a cheap hack to complete the last byte:
	#	if the last byte has bits left, fill these with spaces or
	#	an entry from the huffman tree, which has a code longer than $bit
		if (0 == $bit)
			{
				print STDERR "Wow. Are we lucky, or what? Perfectly aligned bytes...\n" if $self->{verbose};
			}
		else
			{
				CHEAP_HAX0R: {
					FILL_LONG_BIT: {
						my $long;

						eval {
							foreach (values %{$self->{chars}}) {
								die $_ if MAX_COMPRESS_BIT - $bit < 0+@$_;
							}
						};

						last FILL_LONG_BIT unless $@;
						$long = $@;
						printf STDERR "Missing %d bit(s). Padding stream with bits from %d bit element to compensate for alignment...\n", MAX_COMPRESS_BIT - $bit, 0+@$long;

						if (2 == $self->{verbose})
							{
								print STDERR "\tBuffer so far: ";
								printf STDERR "%d", (($n&(1<<$_))?1:0) foreach(0..$bit);
								print STDERR " filling with ";
								printf STDERR "%d", $long->[$_] foreach ($bit..MAX_COMPRESS_BIT-1);
								print STDERR "\n";
							}

						while(MAX_COMPRESS_BIT != $bit)
							{
								for (shift @$long) {
									$n |= (1<<$bit) if $_;
									++$bit;
								}
							}

						push @res,chr($n);
						last CHEAP_HAX0R;
					}

					die sprintf("The stream needs padding, but I couldn't find anything longer than $bit bits in the tree. Is there any use in obfuscating %d bytes of code?", length($$code));
				}
			}

		printf STDERR "Compressed: %d bytes vs. %d bytes (%.3f%%)\n",
			0+@res, 0+@list, 100*((0+@res) / (0+@list)) if $self->{verbose};
		printf STDERR "Compressed: %d bytes vs. %d bytes (%.3f%%)\n",
			0+@res, $bytes_pre, 100*((0+@res) / $bytes_pre) if $self->{verbose} and defined($bytes_pre);

		$$code = $self->to_jstring($self->{block_size}, @res);
		$tree = $self->huffman_to_js($self->{tree});

		for ($Yasco::decompressor4->[$self->{debug} ? 2 : $self->{NO_EXPORTS} ? 1 : 0]) {

			s/#\{COMPRESSED\}/$$code/;
			s/#\{HUFFMAN_TREE\}/$tree/;

			$$code = $_;
		}

	}
##------------------------------------------------------------------------------
sub make_huffman_tree
	{
		my $self = shift;
		my $code = shift;
		my (%chars,@prob_queue,$tree);

		$chars{$_}++ foreach split //, $$code;

		push @prob_queue, {c => $_,n => $chars{$_}} foreach keys %chars;
		$chars{$_->{c}} = $_ foreach @prob_queue;

		for (;1 != 0+@prob_queue;)
			{
				@prob_queue = sort { $a->{n} <=> $b->{n} } @prob_queue;
				my ($l,$r) = (shift @prob_queue, shift @prob_queue);
				my ($node) = { n => $l->{n} + $r->{n}, c => [$l, $r] };

				$l->{p} = \$node;
				$l->{w} = 0;
				$r->{p} = \$node;
				$r->{w} = 1;

				push @prob_queue, $node;
			}

		$tree = $prob_queue[0];

		foreach (keys %chars)
			{
				my $bincode = '';
				my $up = \$chars{$_};

				while (defined($$up->{p}))
					{
						$bincode = $$up->{w} . $bincode;
						$up = $$up->{p};
					}

				$chars{$_} = [map {int} split//, $bincode];
			}

		$self->{tree} = $tree;
		$self->{chars} = {%chars};

		RENDER_TREE: if (2 == $self->{verbose}) {
			(my $fn = $self->{input_name}) =~ s/\.([^.]+)$//;
			my $fp;
			$fn .= '.huffman.html';

			warn("$fn: $!"), last RENDER_TREE
				unless open $fp, '>', $fn;

			$self->render_queue($fp,$prob_queue[0]);
			print $fp '<hr /><pre>'.$$code.'</pre>';
			print $fp '<hr /><pre>'.Dumper(\%chars).'</pre>';
			close $fp;
		}

		1
	}
##------------------------------------------------------------------------------
sub _encode
	{
		my $self = shift;
		my $cont = shift;

		my ($buff,$len,$ptr,$step);
		my ($n,$c,$cl,$l,$lastPerc);

		$len = length $cont;
		$ptr = 0;
		$step = 0;
		$buff = '';
		$lastPerc = 0;

		PROCESS_BYTES: for (;$ptr < $len;$ptr += $l)
			{
				my $lookback = max(0, $ptr - MAX_LOOK_BACK);

				my ($scan_length,$scan_block_start) = (0,0);

				if ($lastPerc != int(100 * $ptr / $len))
					{
						$lastPerc = int(100 * $ptr / $len);
						printf STDERR "\x1B[u\x1B[s\x1B[K%d%%", $lastPerc if $self->{verbose};
					}

				while ($lookback < $ptr)
					{
						my $scan_ptr;

						SCAN_WINDOW: for ($scan_ptr = 0;$scan_ptr < MAX_WINDOW;$scan_ptr++)
							{
								last SCAN_WINDOW if ($lookback + $scan_ptr == $ptr);
								last SCAN_WINDOW if (substr($cont,$lookback + $scan_ptr,1) ne substr($cont,$ptr + $scan_ptr,1));
							}

						if ($scan_length <= $scan_ptr)		# "le" to get it closer to pointer
							{
								$scan_block_start = $lookback;
								$scan_length = $scan_ptr;
							}

						++$lookback;
					}

				$l = $scan_length;

				COPY_ENCODE: {
					last COPY_ENCODE if MIN_LENGTH > $l;

					my $block = substr $cont, $scan_block_start, $scan_length;
					my $offset = $ptr - $scan_block_start;
					my $length = $l - MIN_LENGTH;

					if ($length <= 7 && $offset <= 31)
						{
							$cl = 2;
							$c = ($offset << 3) | $length;
							$cl += 3 if ($c < 0x20 || $c >= 0x7F);
							last COPY_ENCODE if $l < $cl;

							$buff .= '%' . chr($c);
							next PROCESS_BYTES;
						}

					if ($length <= 31 && $offset <= 2047)
						{
							$offset <<= 5;

							$cl = 3;
							$c = $length | (0xff & $offset);
							$cl += 3 if ($c < 0x20 || $c >= 0x7F);
							$c = $offset >> 8;
							$cl += 3 if ($c < 0x20 || $c >= 0x7F);
							last COPY_ENCODE if $l < $cl;

							$buff .= '^' . chr($length | (0xff & $offset)) . chr($offset >> 8);
							next PROCESS_BYTES;
						}

					if ($length <= 63 && $offset <= 1023)
						{
							$cl = 3;
							$offset <<= 6;

							$cl = 3;
							$c = $length | (0xff & $offset);
							$cl += 3 if ($c < 0x20 || $c >= 0x7F);
							$c = $offset >> 8;
							$cl += 3 if ($c < 0x20 || $c >= 0x7F);
							last COPY_ENCODE if $l < $cl;

							$buff .= '~' . chr($length | (0xff & $offset)) . chr($offset >> 8);
							next PROCESS_BYTES;
						}

					if ($offset <= 65535)
						{
							my $diff = $length - 255;

							$cl = 4;

							if (0 < $diff)
								{
									warn sprintf("\n%s\n[TRUNCATING %d BYTES OFF OF LARGE ASS BLOCK @%d+%dbytes: %s\n", ('-'x100), $diff, $offset, $length, $block);
									$length -= $diff;
									$l -= $diff;
								}

							$cl += 3 if $length < 0x20 || $length >= 0x7F;
							$cl += 3 if ((0xff & $offset) < 0x20 || (0xff & $offset) >= 0x7F);
							$cl += 3 if (($offset>>8) < 0x20 || ($offset>>8) >= 0x7F);
							last COPY_ENCODE if $l < $cl;

							$buff .= '@' . chr($length) . chr(0xff & $offset) . chr($offset >> 8);
							next PROCESS_BYTES;
						}

					warn sprintf("\n%s\n[BLOCK FAR FAR AWAY @%d+%dbytes: %s\n", ('-'x100), $offset, $length, $block);
				}

				$l++ if (0 == $l);
				copy_str($cont, $ptr, \$buff, $l);
			}

		$buff
	}
##------------------------------------------------------------------------------
sub copy_str
	{
		my ($src, $off, $dest, $count) = @_;

		for (my $n = 0; $n < $count; $n++)
			{
				my $c = substr($src,$off + $n,1);
				$$dest .= $c,next if $c !~ m/[#%\^@~]/;
				$$dest .= "#$c";
			}
	}
##------------------------------------------------------------------------------
sub render_queue
	{
		my $self = shift;
		my $fp = shift;
		my $queue = shift;

		if (ref $queue->{c}) {
			print $fp '<table cellspacing="2" cellpadding="2" border="0" style="border:1px solid black"><tbody>
			<tr>';
			print $fp '<td colspan="2" align="center">' . $queue->{n} . ' ('.(0+@{$queue->{c}}).') </td>
		</tr><tr>
			<td valign="top">';
			$self->render_queue($fp,$queue->{c}->[1]);
			print $fp '</td><td valign="top">';
			$self->render_queue($fp,$queue->{c}->[0]);
			print $fp '</td>';
			print $fp '</tr></tbody></table>';
		} else {
			print $fp '<table cellspacing="2" cellpadding="2" border="0" style="border:1px solid red"><tbody>
			<tr>';
			printf $fp '<td align="center">%d<br/>%s <code>%s</code></td>', $queue->{n}, $queue->{c}, join('',@{$self->{chars}->{$queue->{c}}});
			print $fp '</tr></tbody></table>';
		}

	}
##------------------------------------------------------------------------------
sub __dump
	{
#		my $data = shift;
#		my @list = map {ord} split //, $data;
		my $off = shift;
		my $caption = shift;
		my @list = @_;
		my ($ptr,$len);

		$len = length $caption;

		print STDOUT "\n----------[ $caption ]";
		print STDOUT ('-' x (58 - $len)) . "\n";
		print STDOUT "     ";
		printf STDOUT "%02X ", $_ foreach (0..7);
		print STDOUT ' ';
		printf STDOUT "%02X ", $_ foreach (8..15);
		print STDOUT ' ';
		printf STDOUT '%X', $_ foreach (0..7);
		print STDOUT ' ';
		printf STDOUT '%X', $_ foreach (8..15);
		print STDOUT "\n";

		for ($len = 0+@list, $ptr = 0; $ptr < $len; $ptr += 16)
			{
				my ($n,$data) = (0,'');
				printf STDOUT "%04X ", $off+$ptr;
				for (; $n < 16 && $ptr + $n < $len; $n++)
					{
						printf STDOUT "%02X ", $list[$ptr+$n];
						$data .= chr($list[$ptr+$n]) if 0x20 <= $list[$ptr+$n] && 0x7F > $list[$ptr+$n];
						$data .= '.' if 0x20 > $list[$ptr+$n] || 0x7F < $list[$ptr+$n];

						$data .= ' ',
						print STDOUT ' ' if 7 == $n;
					}

				while (16 >= ++$n)
					{
						print STDOUT '   ';
						$data .= ' ';

						$data .= ' ',
						print STDOUT ' ' if 7 == $n;
					}

				print STDOUT " $data\n";
			}
	}
##------------------------------------------------------------------------------
sub to_jstring
	{
		my $self = shift;
		my $chunk_width = shift;
		my @parts = @_;
		my @lines;
		my ($p,$c,$line);

		push @lines,'';
		$line = \$lines[-1];

		MK_STRING: while (@parts)
			{
				$p = shift @parts;
				$c = ord $p;

				GET_CHAR: {
					$p = '\n', last GET_CHAR	if 0x0A == $c;
					$p = '\r', last GET_CHAR	if 0x0D == $c;
					$p = '\t', last GET_CHAR	if 0x09 == $c;
					$p = '\\\'', last GET_CHAR	if "'" eq $p;
					$p = '\\\\', last GET_CHAR	if "\\" eq $p;
					last GET_CHAR				if 0x20 <= $c &&
												   0x7F > $c;

					if (0 != $c && (2 <= 0+@parts) && (0x20 >= ord($parts[0]) || 0x7F <= ord($parts[0])))
						{
							$p = shift @parts;
							$p = sprintf('\\u%02X%02X', $c, ord($p));
						}
					else
						{
							$p = sprintf('\\x%02X', $c);
						}
				}

				$$line .= $p;
				next if $chunk_width > length $$line;
				push @lines, '';
				$line = \$lines[-1];
			}

		join "+\n", map {"'$_'"} @lines;
	}
##------------------------------------------------------------------------------
sub huffman_to_js
	{
		my $self = shift;
		my $node = shift;
		my $res;

		if (ref $node->{c})
			{
				$res	= '{0:'
						. $self->huffman_to_js($node->{c}->[0])
						. ',1:'
						. $self->huffman_to_js($node->{c}->[1])
						. '}';
			}
		else
			{
				$res = ord $node->{c};
			}

		$res;
	}
##------------------------------------------------------------------------------
1
__END__
eval(function(_5,_4){
var _0,L='length',F='fromCharCode',_l=_5[L],_1=0,_O=0,_3=null,_2='',_I=_4,_=[];_[0]=0;for(;_1<_l||_[0]!=0;){for(;;){if(0>--_O){if(_1==_l)return _2;_0=o();_O=6}_I=_I[_0&1];_0>>=1;if('number'==typeof _I)break}if(_[0]){if(0x23==_[0]){_2+=String[F](_I);_=[0]}else if(--_[1])_.push(_I);else{var l,I;if(0x25==_[0]){l=_I&7;I=_I>>3}else if(0x5E==_[0]){l=_[2]&31;_[2]&=~31;_I<<=8;I=(_[2]|_I)>>5}else if(0x7E==_[0]){l=_[2]&63;_[2]&=~63;_I<<=8;I=(_[2]|_I)>>6}else{l=_[2];I=_[3]|(_I<<8)}_2+=_2.substr(_2[L]-I,l+3);_=[0]}}else if(0x23==_I)_=[_I,1];else if(0x25==_I)_=[_I,1];else if(0x5E==_I||0x7E==_I)_=[_I,2];else if(0x40==_I)_=[_I,3];else _2+=String[F](_I);_I=_4}return _2;function o(){var _0;if(null!==_3){_0=_3;_3=null}else{_0=_5.charCodeAt(_1++);if(0xFF<_0){_3=_0&0xFF;_0>>=8}}return _0}
}(#{COMPRESSED},#{HUFFMAN_TREE}));
=====
(function(_5,_4){
var _0,L='length',F='fromCharCode',_l=_5[L],_1=0,_O=0,_3=null,_2='',_I=_4,_=[];_[0]=0;for(;_1<_l||_[0]!=0;){for(;;){if(0>--_O){if(_1==_l)return _2;_0=o();_O=6}_I=_I[_0&1];_0>>=1;if('number'==typeof _I)break}if(_[0]){if(0x23==_[0]){_2+=String[F](_I);_=[0]}else if(--_[1])_.push(_I);else{var l,I;if(0x25==_[0]){l=_I&7;I=_I>>3}else if(0x5E==_[0]){l=_[2]&31;_[2]&=~31;_I<<=8;I=(_[2]|_I)>>5}else if(0x7E==_[0]){l=_[2]&63;_[2]&=~63;_I<<=8;I=(_[2]|_I)>>6}else{l=_[2];I=_[3]|(_I<<8)}_2+=_2.substr(_2[L]-I,l+3);_=[0]}}else if(0x23==_I)_=[_I,1];else if(0x25==_I)_=[_I,1];else if(0x5E==_I||0x7E==_I)_=[_I,2];else if(0x40==_I)_=[_I,3];else _2+=String[F](_I);_I=_4}new Function(_2)();function o(){var _0;if(null!==_3){_0=_3;_3=null}else{_0=_5.charCodeAt(_1++);if(0xFF<_0){_3=_0&0xFF;_0>>=8}}}
})(#{COMPRESSED},#{HUFFMAN_TREE});
=====
try {
	var jscript = (function(code,tree) {
		var c,
			len = code.length,
			pos = 0,
			bit = 0,
			hold = null,
			res = '',
			leaf = tree,
			params = new Array();

		params[0] = 0;

		for (;pos < len||0!=params[0];)
			{
				for (;;)
					{
						if (0 > --bit) {
							if (pos == len) return res;
							c = next();
							bit = 6;
						}

						leaf = leaf[c & 1];
						c >>= 1;
						if ('number'==typeof leaf)
							{
								break;
							}
					}

				if (params[0])
					{
						if (0x23 == params[0]) {
							res += String.fromCharCode(leaf);
							params=[0];
						} else if (--params[1])
							params.push(leaf);
						else
							{
								var copy_length, copy_from;

								if (0x25 == params[0])		// '%'
									{
										copy_length = leaf&7;
										copy_from = leaf>>3;
									}
								else if (0x5E == params[0])	// '^'
									{
										copy_length = params[2] & 31;
										params[2] &= ~31;
										leaf <<= 8;
										copy_from = (params[2]|leaf)>>5;
									}
								else if (0x7E == params[0])	// '~'
									{
										copy_length = params[2] & 63;
										params[2] &= ~63;
										leaf <<= 8;
										copy_from = (params[2]|leaf)>>6;
									}
								else	// '@'
									{
										if (0x40 != params[0]) throw new Error('Yargh.');
										copy_length = params[2];
										copy_from = params[3] | (leaf<<8);
									}

								res += res.substr(res.length - copy_from, copy_length + 3);
								params=[0];
							}
					}
				else if (0x23 == leaf)		// '#'
					params=[leaf,1];
				else if (0x25 == leaf)		// '%'
					params=[leaf,1];
				else if (0x5E == leaf || 0x7E == leaf)	// '^' / '~'
					params=[leaf,2];
				else if (0x40 == leaf)	// '@'
					params=[leaf,3];
				else
					res += String.fromCharCode(leaf);

				leaf = tree;
			}

	return res;

	function next()
		{
			var c;

			if (null !== hold)
				{
					c = hold;
					hold = null;
				}
			else
				{
					c = code.charCodeAt(pos++);
					if (0xFF < c)
						{
							hold = c & 0xFF;
							c >>= 8;
						}
				}

			return c;
		}

	})(#{COMPRESSED},#{HUFFMAN_TREE});

	document.write('<hr/><pre>'+jscript.escapeHTML()+'</pre>');
	eval(jscript);
} catch(E) {
	document.write('<pre>'+E+'</pre>');
	throw E;
}
